// Server network command handlers for HaxServ
//
// Written by: Test_User <hax@andrewyu.org>
//
// This is free and unencumbered software released into the public
// domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

#include <gnutls/gnutls.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include "network.h"
#include "types.h"
#include "table.h"
#include "tls.h"
#include "config.h"
#include "utils.h"
#include "commands.h"

pthread_mutex_t send_lock = PTHREAD_MUTEX_INITIALIZER;

int resolve(char *address, char *port, struct sockaddr *sockaddr) {
	int success;
	struct addrinfo hints = {0}, *info;

	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;
	hints.ai_flags = AI_NUMERICSERV | AI_ADDRCONFIG;

	success = getaddrinfo(address, port, &hints, &info);

	if (success == 0) {
		*sockaddr = *(info->ai_addr);
		freeaddrinfo(info);
	}

	return success;
}

struct table server_network_commands = {0};
struct table server_list = {0};
struct table user_list = {0};
struct table channel_list = {0};

int ping_handler(struct string sender, uint64_t argc, struct string *argv) {
	if (argc < 2) {
		puts("Invalid PING recieved! (Missing parameters)");
		return 1;
	}

	uint64_t len = 1 + argv[1].len + 6 + argv[1].len + 1 + sender.len + 1;
	char msg[len];
	uint64_t offset = 0;
	msg[0] = ':';
	offset++;
	memcpy(msg+offset, argv[1].data, argv[1].len);
	offset += argv[1].len;
	memcpy(msg+offset, " PONG ", 6);
	offset += 6;
	memcpy(msg+offset, argv[1].data, argv[1].len);
	offset += argv[1].len;
	msg[offset] = ' ';
	offset++;
	memcpy(msg+offset, sender.data, sender.len);
	offset += sender.len;
	msg[offset] = '\n';

	SEND(((struct string){msg, len}));

	return 0;
}

int server_handler(struct string sender, uint64_t argc, struct string *argv) {
	if (argc < 5) {
		puts("Invalid SERVER recieved! (Missing parameters)");
		return 1;
	}

	if (argv[2].len > 20) {
		puts("Invalid SERVER recieved! (Distance too large)");
		return 1;
	}

	char err;
	uint64_t distance = str_to_unsigned(argv[2], &err);
	if (err) {
		puts("Invalid SERVER recieved! (Invalid distance given)");
		return 1;
	}

	if (sender.len != 0) {
		struct server_info *from = get_table_index(server_list, sender);
		if (!from) {
			puts("Invalid SERVER recieved! (Unknown source)");
			return 1;
		}

		distance += from->distance + 1;
	}

	if (get_table_index(server_list, argv[3]) != 0) {
		WRITES(2, STRING("Invalid SERVER recieved! (Duplicate SID already connected)\n"));
		return 1;
	}

	struct string address;
	address.data = malloc(argv[0].len);
	if (address.data == 0)
		goto server_handler_oom;

	address.len = argv[0].len;
	memcpy(address.data, argv[0].data, argv[0].len);

	struct string name;
	name.data = malloc(argv[4].len);
	if (name.data == 0)
		goto server_handler_free_address;

	name.len = argv[4].len;
	memcpy(name.data, argv[4].data, argv[4].len);

	struct server_info *server = malloc(sizeof(*server));
	if (server == 0)
		goto server_handler_free_name;

	struct string via;
	if (sender.len != 0) { // connected to the sender
		via.data = malloc(sender.len);
		if (!via.data)
			goto server_handler_free_server;

		via.len = sender.len;
		memcpy(via.data, sender.data, sender.len);
	} else { // connected directly to us
		via = (struct string){0};
	}

	*server = (struct server_info){
		.name = name,
		.address = address,
		.distance = distance,
		.via = via,
	};

	if (set_table_index(&server_list, argv[3], server) != 0)
		goto server_handler_free_via;

	return 0;

	server_handler_free_via:
	if (sender.len != 0)
		free(via.data);
	server_handler_free_server:
	free(server);
	server_handler_free_name:
	free(name.data);
	server_handler_free_address:
	free(address.data);
	server_handler_oom:
	puts("OOM! (server_handler)");

	return 1;
}

int uid_handler(struct string sender, uint64_t argc, struct string *argv) {
	if (argc < 10) {
		puts("Invalid UID recieved! (Missing parameters)");
		return 1;
	}

	if (argv[1].len > 20 || argv[7].len > 20) {
		puts("Invalid UID recieved! (Timestamp too long)");
		return 1;
	}

	if (sender.len == 0) {
		puts("Invalid UID recieved! (No source)");
		return 1;
	}

	if (get_table_index(user_list, argv[0])) {
		WRITES(2, STRING("Invalid UID revieved! (Attempted to create already-existing user)\n"));
		return 1;
	}

	// TODO: modes

	char err;
	uint64_t nick_ts = str_to_unsigned(argv[1], &err);
	if (err) {
		puts("Invalid UID recieved! (Invalid nick timestamp)");
		return 1;
	}

	uint64_t user_ts = str_to_unsigned(argv[7], &err);
	if (err) {
		puts("Invalid UID recieved! (Invalid user timestamp)");
		return 1;
	}

	struct string server;
	server.data = malloc(sender.len);
	if (!server.data)
		goto uid_handler_oom;
	server.len = sender.len;
	memcpy(server.data, sender.data, sender.len);

	struct string nick;
	nick.data = malloc(argv[2].len);
	if (!nick.data)
		goto uid_handler_free_server;
	nick.len = argv[2].len;
	memcpy(nick.data, argv[2].data, argv[2].len);

	struct string hostname;
	hostname.data = malloc(argv[3].len);
	if (!hostname.data)
		goto uid_handler_free_nick;
	hostname.len = argv[3].len;
	memcpy(hostname.data, argv[3].data, argv[3].len);

	struct string vhost;
	vhost.data = malloc(argv[4].len);
	if (!vhost.data)
		goto uid_handler_free_hostname;
	vhost.len = argv[4].len;
	memcpy(vhost.data, argv[4].data, argv[4].len);

	struct string ident;
	ident.data = malloc(argv[5].len);
	if (!ident.data)
		goto uid_handler_free_vhost;
	ident.len = argv[5].len;
	memcpy(ident.data, argv[5].data, argv[5].len);

	struct string ip;
	ip.data = malloc(argv[6].len);
	if (!ip.data)
		goto uid_handler_free_ident;
	ip.len = argv[6].len;
	memcpy(ip.data, argv[6].data, argv[6].len);

	struct string realname;
	realname.data = malloc(argv[argc - 1].len);
	if (!realname.data)
		goto uid_handler_free_ip;
	realname.len = argv[argc - 1].len;
	memcpy(realname.data, argv[argc - 1].data, argv[argc - 1].len);

	struct user_info *user = malloc(sizeof(*user));
	if (!user)
		goto uid_handler_free_realname;

	*user = (struct user_info){
		.nick_ts = nick_ts,
		.user_ts = user_ts,
		.server = server,
		.nick = nick,
		.hostname = hostname,
		.vhost = vhost,
		.ident = ident,
		.ip = ip,
		.realname = realname,
		.opertype = {.data = malloc(0), .len = 0},
		.metadata = {.array = malloc(0), .len = 0},
	};

	if (set_table_index(&user_list, argv[0], user) != 0)
		goto uid_handler_free_user;

	return 0;

	uid_handler_free_user:
	free(user);
	uid_handler_free_realname:
	free(realname.data);
	uid_handler_free_ip:
	free(ip.data);
	uid_handler_free_ident:
	free(ident.data);
	uid_handler_free_vhost:
	free(vhost.data);
	uid_handler_free_hostname:
	free(hostname.data);
	uid_handler_free_nick:
	free(nick.data);
	uid_handler_free_server:
	free(server.data);
	uid_handler_oom:
	puts("OOM! (uid_handler)");

	return 1;
}

int opertype_handler(struct string sender, uint64_t argc, struct string *argv) {
	if (argc < 1) {
		WRITES(2, STRING("Invalid OPERTYPE recieved! (Missing parameters)\n"));
		return 1;
	}

	if (sender.len == 0) {
		WRITES(2, STRING("Invalid OPERTYPE recieved! (No source)\n"));
		return 1;
	}

	struct user_info *user = get_table_index(user_list, sender);
	if (!user) {
		WRITES(2, STRING("Server "));
		WRITES(2, sender);
		WRITES(2, STRING(" attempted to set OPERTYPE on a nonexistent user!\n"));
		return 1;
	}

	struct string opertype = {.data = malloc(argv[0].len), .len = argv[0].len};
	if (!opertype.data) {
		WRITES(2, STRING("OOM! (opertype_handler)\n"));
		return 1;
	}
	memcpy(opertype.data, argv[0].data, argv[0].len);

	free(user->opertype.data);

	user->opertype = opertype;

	return 0;
}

int quit_handler(struct string sender, uint64_t argc, struct string *argv) {
	if (argc < 1)
		return remove_user(sender, (struct string){0});
	else
		return remove_user(sender, argv[0]);
}

int kill_handler(struct string sender, uint64_t argc, struct string *argv) {
	if (argc < 2) {
		WRITES(2, STRING("Invalid KILL recieved! (Missing parameters)\n"));
		return 1;
	}

	// TODO: Get accurate list of what got killed, what to rejoin, etc

	struct user_info *user = get_table_index(user_list, argv[0]);
	if (!user)
		return 0; // TODO: Currently not all local users are considered; fix that, then make this give an error

	if (STRING_EQ(user->server, STRING("1HC"))) {
		SEND(STRING(":"));
		SEND(argv[0]);
		SEND(STRING(" QUIT :"));
		SEND(argv[1]);
		SEND(STRING("\n"));

		SEND(STRING("GLOADMODULE m_servprotect\n")); // required for the +k we're about to use

		char user_time[21];
		snprintf(user_time, 21, "%ld", user->user_ts);
		char nick_time[21];
		snprintf(nick_time, 21, "%ld", user->nick_ts);
		SEND(STRING("UID "));
		SEND(argv[0]);
		SEND(STRING(" "));
		SEND(NULSTR(user_time));
		SEND(STRING(" "));
		SEND(user->nick);
		SEND(STRING(" "));
		SEND(user->hostname);
		SEND(STRING(" "));
		SEND(user->vhost);
		SEND(STRING(" "));
		SEND(user->ident);
		SEND(STRING(" "));
		SEND(user->ip);
		SEND(STRING(" "));
		SEND(NULSTR(nick_time));
		SEND(STRING(" +k :"));
		SEND(user->realname);
		SEND(STRING("\n"));
		if (STRING_EQ(argv[0], STRING("1HC000001"))) {
			SEND(STRING(":1HC METADATA 1HC000001 ssl_cert :vTrse "));
			SEND(client_cert);
			SEND(STRING("\n:"));
			SEND(argv[0]);
			SEND(STRING(" OPERTYPE "));
			SEND(opertype);
			SEND(STRING("\n"));
		}

		for (uint64_t i = 0; i < channel_list.len; i++) {
			struct channel_info *channel = channel_list.array[i].ptr;
			if (has_table_index(channel->user_list, argv[0])) {
				char timestamp[21];
				SEND(STRING("FJOIN "));
				SEND(channel_list.array[i].name);
				SEND(STRING(" "));
				snprintf(timestamp, 21, "%ld", channel->ts);
				SEND(NULSTR(timestamp));
				SEND(STRING(" + :,"));
				SEND(argv[0]);
				SEND(STRING("\n"));
			}
		}
	} else {
		return remove_user(argv[0], argv[1]);
	}

	return 0;
}

int nick_handler(struct string sender, uint64_t argc, struct string *argv) {
	if (argc < 2) {
		WRITES(2, STRING("Invalid NICK recieved! (Missing parameters)\n"));
		return 1;
	}

	struct user_info *info = get_table_index(user_list, sender);
	if (!info) {
		WRITES(2, STRING("NICK: Unknown user!\n"));
		return 1;
	}

	for (uint64_t i = 0; i < channel_list.len; i++) { // TODO: More efficient way of doing this
		struct channel_info *channel = channel_list.array[i].ptr;
		if (has_table_index(channel->user_list, sender) && has_table_index(channel->user_list, STRING("1HC000001"))) {
			SENDCLIENT(STRING(":"));
			SENDCLIENT(info->nick);
			SENDCLIENT(STRING("!"));
			SENDCLIENT(info->ident);
			SENDCLIENT(STRING("@"));
			SENDCLIENT(info->vhost);
			SENDCLIENT(STRING(" NICK :"));
			SENDCLIENT(argv[0]);
			SENDCLIENT(STRING("\r\n"));

			break;
		}
	}

	void *tmp = malloc(argv[0].len);
	if (!tmp) {
		WRITES(2, STRING("OOM! (nick_handler)\n"));
		return 1;
	}
	memcpy(tmp, argv[0].data, argv[0].len);

	free(info->nick.data);
	info->nick.data = tmp;
	info->nick.len = argv[0].len;

	if (argv[1].len > 20) {
		WRITES(2, STRING("Invalid NICK recieved! (Timestamp too long)\n"));
		return 1;
	}

	char err;
	uint64_t ts = str_to_unsigned(argv[1], &err);
	if (err) {
		WRITES(2, STRING("Invalid NICK recieved! (Invalid timestamp)\n"));
		return 1;
	}
	info->nick_ts = ts;

	return 0;
}

int fjoin_handler(struct string sender, uint64_t argc, struct string *argv) {
	if (argc < 4) {
		WRITES(2, STRING("Invalid FJOIN recieved! (Missing parameters"));
		return 1;
	}

	char err;
	uint64_t timestamp = str_to_unsigned(argv[1], &err);
	if (err) {
		WRITES(2, STRING("Invalid FJOIN recieved! (Invalid timestamp given)\n"));
		return 1;
	}

	// TODO: Parse modes, then make the rest of this work
	uint64_t userlist_offset = 3;
	{
		char dir = '?';
		for (uint64_t offset = 0; offset < argv[2].len; offset++) {
			if (argv[2].data[offset] == '+') {
				dir = '+';
			} else if (argv[2].data[offset] == '-') {
				dir = '-';
			} else if (dir == '?') {
				WRITES(2, STRING("Invalid FJOIN recieved! (No direction set for modes)\n"));
				return 1;
			} else if (channel_mode_types[(unsigned char)argv[2].data[offset]] == MODE_TYPE_UNKNOWN) {
				WRITES(2, STRING("Invalid FJOIN recieved! (Unknown mode set on the channel)\n"));
				return 1;
			} else if (channel_mode_types[(unsigned char)argv[2].data[offset]] != MODE_TYPE_NOARGS && dir == '+') {
				userlist_offset++;
			}
		}
	}

	if (argc < userlist_offset + 1) {
		WRITES(2, STRING("Invalid FJOIN recieved! (Missing mode parameters or user list)\n"));
		return 1;
	}

	struct channel_info *channel = get_table_index(channel_list, argv[0]);
	if (!channel) {
		channel = malloc(sizeof(*channel));
		if (!channel) {
			WRITES(2, STRING("OOM! (fjoin_handler)\n"));
			return 1;
		}
		*channel = (struct channel_info){
			.ts = timestamp,
			.topic = {.data = malloc(0), .len = 0},
			.topic_ts = 0,
			.modes = {.array = malloc(0), .len = 0},
			.user_list = {.array = malloc(0), .len = 0},
			.metadata = {.array = malloc(0), .len = 0},
		};

		set_table_index(&channel_list, argv[0], channel);
	}
	if (timestamp < channel->ts)
		channel->ts = timestamp;

	struct string userlist = argv[userlist_offset];

	if (userlist.len < 10) // Not enough for any users
		return 0;

	uint8_t sendclient = has_table_index(channel->user_list, STRING("1HC000001"));

	uint64_t offset = 0;
	while (1) {
		while (offset < userlist.len && userlist.data[offset] != ',')
			offset++;

		if (offset > (userlist.len - 1) - 9)
			break;

		offset++;

		struct string user = {.data = &(userlist.data[offset]), .len = 9};
		struct user_info *user_info = get_table_index(user_list, user);

		set_table_index(&(channel->user_list), user, user_info);

		if (user_info && sendclient) {
			SENDCLIENT(STRING(":"));
			SENDCLIENT(user_info->nick);
			SENDCLIENT(STRING("!"));
			SENDCLIENT(user_info->ident);
			SENDCLIENT(STRING("@"));
			SENDCLIENT(user_info->vhost);
			SENDCLIENT(STRING(" JOIN :"));
			SENDCLIENT(argv[0]);
			SENDCLIENT(STRING("\r\n"));
		}

		offset += 10;
	}

	return 0;
}

int squit_handler(struct string sender, uint64_t argc, struct string *argv) {
	if (argc < 1) {
		WRITES(2, STRING("Invalid SQUIT recieved! (Missing parameters)\n"));
		return 1;
	}

	struct server_info *server = remove_table_index(&server_list, argv[0]);
	if (!server) {
		WRITES(2, STRING("Invalid SQUIT received! (Unknown server)\n"));
		WRITES(2, STRING("Known servers:\r\n"));
		for (uint64_t i = 0; i < server_list.len; i++) {
			WRITES(2, server_list.array[i].name);
			WRITES(2, STRING("\r\n"));
		}
		return 1;
	}

	for (uint64_t i = 0; i < user_list.len;) {
		struct user_info *info = user_list.array[i].ptr;
		if (STRING_EQ(info->server, argv[0])) {
			remove_user(user_list.array[i].name, STRING("*.net *.split"));
		} else {
			i++; // removal of the user from the table shifts the next user into place for us
		}
	}

	free(server->name.data);
	free(server->address.data);
	if (server->via.data != 0)
		free(server->via.data);
	free(server);

	return 0;
}

int privmsg_handler(struct string sender, uint64_t argc, struct string *argv) {
	if (argc < 2) {
		WRITES(2, STRING("Invalid PRIVMSG recieved (Missing parameters)\n"));
		return 1;
	}

	if (sender.len == 0) {
		WRITES(2, STRING("Invalid PRIVMSG recieved (No source)\n"));
		return 1;
	}

	struct user_info *user = get_table_index(user_list, sender);
	struct server_info *server;
	if (user) {
		server = 0;
		if (argv[0].data[0] == '#') {
			struct channel_info *channel = get_table_index(channel_list, argv[0]);
			if (channel && has_table_index(channel->user_list, STRING("1HC000001"))) {
				SENDCLIENT(STRING(":"));
				SENDCLIENT(user->nick);
				SENDCLIENT(STRING("!"));
				SENDCLIENT(user->ident);
				SENDCLIENT(STRING("@"));
				SENDCLIENT(user->vhost);
				SENDCLIENT(STRING(" PRIVMSG "));
				SENDCLIENT(argv[0]);
				SENDCLIENT(STRING(" :"));
				SENDCLIENT(argv[1]);
				SENDCLIENT(STRING("\r\n"));
			}
		} else if (STRING_EQ(argv[0], STRING("1HC000001"))) {
			SENDCLIENT(STRING(":"));
			SENDCLIENT(user->nick);
			SENDCLIENT(STRING("!"));
			SENDCLIENT(user->ident);
			SENDCLIENT(STRING("@"));
			SENDCLIENT(user->vhost);
			SENDCLIENT(STRING(" PRIVMSG "));
			SENDCLIENT(client_nick);
			SENDCLIENT(STRING(" :"));
			SENDCLIENT(argv[1]);
			SENDCLIENT(STRING("\r\n"));
		}
	} else {
		server = get_table_index(server_list, sender);
		if (server) {
			if (argv[0].data[0] == '#') {
				struct channel_info *channel = get_table_index(channel_list, argv[0]);
				if (channel && has_table_index(channel->user_list, STRING("1HC000001"))) {
					SENDCLIENT(STRING(":"));
					SENDCLIENT(server->address);
					SENDCLIENT(STRING(" PRIVMSG "));
					SENDCLIENT(argv[0]);
					SENDCLIENT(STRING(" :"));
					SENDCLIENT(argv[1]);
					SENDCLIENT(STRING("\r\n"));
				}
			} else if (STRING_EQ(argv[0], STRING("1HC000001"))) {
				SENDCLIENT(STRING(":"));
				SENDCLIENT(server->address);
				SENDCLIENT(STRING(" PRIVMSG "));
				SENDCLIENT(client_nick);
				SENDCLIENT(STRING(" :"));
				SENDCLIENT(argv[1]);
				SENDCLIENT(STRING("\r\n"));
			}
		}
	}

	uint64_t offset;
	if (argv[0].data[0] == '#') {
		if (argv[1].len < command_prefix.len || memcmp(argv[1].data, command_prefix.data, command_prefix.len) != 0)
			return 0;

		struct channel_info *channel_info = get_table_index(channel_list, argv[0]);
		if (!channel_info)
			return 0;

		if (!has_table_index(channel_info->user_list, STRING("1HC000000")))
			return 0;

		offset = command_prefix.len;
	} else if (STRING_EQ(argv[0], STRING("1HC000000"))) {
		offset = 0;
	} else {
		return 0;
	}

	if (offset >= argv[1].len || argv[1].data[offset] == ' ')
		return 0;

	uint64_t command_argc = 0;
	uint64_t old_offset = offset;
	while (offset < argv[1].len) {
		while (offset < argv[1].len && argv[1].data[offset] != ' ')
			offset++;

		command_argc++;

		while (offset < argv[1].len && argv[1].data[offset] == ' ')
			offset++;
	}
	offset = old_offset;

	struct string command_argv[command_argc]; // argv[0] in this case is the command itself, unlike network command handlers... might change one of these two later to match
	uint64_t i = 0;
	while (offset < argv[1].len) {
		command_argv[i].data = argv[1].data+offset;
		uint64_t start = offset;

		while (offset < argv[1].len && argv[1].data[offset] != ' ')
			offset++;

		command_argv[i].len = offset - start;

		while (offset < argv[1].len && argv[1].data[offset] == ' ')
			offset++;

		i++;
	}

	argv[1].data += old_offset;
	argv[1].len -= old_offset;
	struct command_def *cmd = get_table_index(user_commands, command_argv[0]);
	if (cmd) {
		if (!cmd->local_only) {
			if (cmd->privs.len != 0 && sender.len != 3) { // servers always count as oper :P
				if (!user) {
					WRITES(2, STRING("User is unknown!\n"));

					SEND(STRING(":1HC000000 NOTICE "));
					if (argv[0].data[0] == '#')
						SEND(argv[0]);
					else
						SEND(sender);
					SEND(STRING(" :You don't seem to exist... and neither do I!\n"));

					return 1; // have already desynced
				}

				if (user->opertype.len != cmd->privs.len || memcmp(user->opertype.data, cmd->privs.data, cmd->privs.len)) {
					// TODO: complain about missing privs
					SEND(STRING(":1HC000000 NOTICE "));
					if (argv[0].data[0] == '#')
						SEND(argv[0]);
					else
						SEND(sender);
					SEND(STRING(" :You are not authorized to execute this command.\n"));

					return 0;
				}
			}

			struct string message[] = {
				user ? STRING("User ") : (server ? STRING("Server ") : STRING("An unknown user (something desycned... this shouldn't happen)")),
				user ? user->nick : (server ? server->address : STRING("")),
				STRING(" executes `"),
				argv[1],
				STRING("'"),
			};

			privmsg(STRING("1HC000000"), log_channel, sizeof(message)/sizeof(*message), message);

			return cmd->func(sender, argv[1], argv[0].data[0] == '#' ? argv[0] : sender, command_argc, command_argv, 0);
		} else {
			SEND(STRING(":1HC000000 NOTICE "));
			if (argv[0].data[0] == '#')
				SEND(argv[0]);
			else
				SEND(sender);
			SEND(STRING(" :You are not authorized to execute this command.\n"));

			WRITES(1, STRING("Not executing local-only command from a remote source!\n"));
			return 0;
		}
	} else {
		SEND(STRING(":1HC000000 NOTICE "));
		if (argv[0].data[0] == '#')
			SEND(argv[0]);
		else
			SEND(sender);
		SEND(STRING(" :Unknown command: "));
		if (argv[0].data[0] == '#')
			SEND(command_prefix);
		SEND(command_argv[0]);
		SEND(STRING("\n"));
		return 0;
	}
}

int part_handler(struct string sender, uint64_t argc, struct string *argv) {
	if (argc < 1) {
		WRITES(2, STRING("Invalid PART received! (Missing parameters)\n"));
		return 1;
	}

	struct channel_info *channel = get_table_index(channel_list, argv[0]);
	struct user_info *user = get_table_index(user_list, sender);
	if (user && channel && has_table_index(channel->user_list, STRING("1HC000001"))) {
		SENDCLIENT(STRING(":"));
		SENDCLIENT(user->nick);
		SENDCLIENT(STRING("!"));
		SENDCLIENT(user->ident);
		SENDCLIENT(STRING("@"));
		SENDCLIENT(user->vhost);
		SENDCLIENT(STRING(" PART "));
		SENDCLIENT(argv[0]);
		if (argc >= 2) {
			SENDCLIENT(STRING(" :"));
			SENDCLIENT(argv[1]);
		}
		SENDCLIENT(STRING("\r\n"));
	}

	if (channel)
		remove_table_index(&(channel->user_list), sender);

	return 0;
}

int notice_handler(struct string sender, uint64_t argc, struct string *argv) {
	if (argc < 2) {
		WRITES(2, STRING("Invalid NOTICE received! (Missing parameters)\n"));
		return 1;
	}

	struct user_info *user = get_table_index(user_list, sender);
	struct server_info *server;
	if (user)
		server = 0;
	else
		server = get_table_index(server_list, sender);

	if (!user && !server) {
		WRITES(2, STRING("Invalid NOTICE received! (Unknown source)\n"));
		return 1;
	}

	if (argv[0].data[0] == '#') {
		struct channel_info *channel = get_table_index(channel_list, argv[0]);
		if (channel && has_table_index(channel->user_list, STRING("1HC000001"))) {
			SENDCLIENT(STRING(":"));
			if (user) {
				SENDCLIENT(user->nick);
				SENDCLIENT(STRING("!"));
				SENDCLIENT(user->ident);
				SENDCLIENT(STRING("@"));
				SENDCLIENT(user->vhost);
			} else {
				SENDCLIENT(server->address);
			}

			SENDCLIENT(STRING(" NOTICE "));
			SENDCLIENT(argv[0]);
			SENDCLIENT(STRING(" :"));
			SENDCLIENT(argv[1]);
			SENDCLIENT(STRING("\r\n"));
		}
	} else if (STRING_EQ(argv[0], STRING("1HC000001"))) {
		SENDCLIENT(STRING(":"));
		if (user) {
			SENDCLIENT(user->nick);
			SENDCLIENT(STRING("!"));
			SENDCLIENT(user->ident);
			SENDCLIENT(STRING("@"));
			SENDCLIENT(user->vhost);
		} else {
			SENDCLIENT(server->address);
		}

		SENDCLIENT(STRING(" NOTICE "));
		SENDCLIENT(argv[0]);
		SENDCLIENT(STRING(" :"));
		SENDCLIENT(argv[1]);
		SENDCLIENT(STRING("\r\n"));
	}

	return 0;
}

int initservernetwork(void) {
	server_network_commands.array = malloc(0);
	server_list.array = malloc(0);
	user_list.array = malloc(0);
	channel_list.array = malloc(0);

	set_table_index(&server_network_commands, STRING("PING"), &ping_handler);
	set_table_index(&server_network_commands, STRING("SERVER"), &server_handler);
	set_table_index(&server_network_commands, STRING("UID"), &uid_handler);
	set_table_index(&server_network_commands, STRING("OPERTYPE"), &opertype_handler);
	set_table_index(&server_network_commands, STRING("PRIVMSG"), &privmsg_handler);
	set_table_index(&server_network_commands, STRING("QUIT"), &quit_handler);
	set_table_index(&server_network_commands, STRING("KILL"), &kill_handler);
	set_table_index(&server_network_commands, STRING("NICK"), &nick_handler);
	set_table_index(&server_network_commands, STRING("FJOIN"), &fjoin_handler);
	set_table_index(&server_network_commands, STRING("SQUIT"), &squit_handler);
	set_table_index(&server_network_commands, STRING("PART"), &part_handler);
	set_table_index(&server_network_commands, STRING("NOTICE"), &notice_handler);

	init_user_commands();

	int retval = connect_tls();
	if (retval != 0) {
		printf("connect_tls(): %d\n", retval);
		return 1;
	}

	// probably inefficient to be calling SSL_write this frequently, but also less effort
	SEND(STRING("CAPAB START 1202\nCAPAB END\n"));
	SEND(STRING("SERVER "));
	SEND(server_name);
	SEND(STRING(" "));
	SEND(send_password);
	SEND(STRING(" 0 1HC :HaxServ\n"));
	SEND(STRING("BURST "));
	time_t current_time = time(NULL);
	char current_time_str[21]; // C HaxServ will be deprecated long before we reach 20-digit timestamps
	snprintf(current_time_str, 21, "%ld", current_time);
	SEND(NULSTR(current_time_str));
	SEND(STRING("\n"));
	if (add_local_client(STRING("1HC000000"), nick, hostmask, nick, nick, current_time, 0) != 0)
		return 1;

	struct user_info *user_info = get_table_index(user_list, STRING("1HC000000"));

	for (uint64_t i = 0; i < num_prejoin_channels; i++) {
		struct channel_info *channel;
		channel = malloc(sizeof(*channel));
		if (!channel)
			return 1;
		*channel = (struct channel_info){
			.ts = (uint64_t)current_time,
			.topic = {.data = malloc(0), .len = 0},
			.topic_ts = 0,
			.modes = {.array = malloc(0), .len = 0},
			.user_list = {.array = malloc(0), .len = 0},
			.metadata = {.array = malloc(0), .len = 0},
		};
		set_table_index(&channel_list, prejoin_channels[i], channel);
		set_table_index(&(channel->user_list), STRING("1HC000000"), user_info);
		SEND(STRING("FJOIN "));
		SEND(prejoin_channels[i]);
		SEND(STRING(" "));
		SEND(NULSTR(current_time_str));
		SEND(STRING(" + :,1HC000000\nMODE "));
		SEND(prejoin_channels[i]);
		SEND(STRING(" +o 1HC000000\n"));
	}

	SEND(STRING("ENDBURST\n"));

	return 0;
}
